package s

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"path"
	"runtime"
	"sync"
	"time"

	"gitee.com/dark.H/go-utils/o"
)

var (
	home, _ = os.UserHomeDir()
	/* CSTR_EXPECT
	set a str expect input bytes if need input within cmd.
	*/
	CSTR_EXPECT = 0b1000
	CSTR_TYPE   = 0b0100
	CSTR_STDOUT = 0b0001
	CSTR_STDIN  = 0b0010
	/* CSTR_IOINPUT
	set use stdinput if need a input within cmd.
	*/
	CSTR_INTERACT = 0b0011
)

type CStr struct {
	Str
	startCheck bool
	delay      int
	lock       *sync.RWMutex
	env        Dict[Str, Str]
	running    Dict[Str, int]
	outhistory List[Str]
	history    List[Str]
}

func (str Str) AsCmd(cdPath ...any) CStr {
	c := CStr{
		Str:     str,
		lock:    &sync.RWMutex{},
		env:     make(Dict[Str, Str]),
		running: make(Dict[Str, int]),
	}
	c.env["cwd"] = "."
	if cdPath != nil {
		c = c.Cd(cdPath[0])
	}
	return c
}

func (cstr CStr) Self() Str {
	s, err := o.Executable()
	if err != nil {
		SafePushErr(err)
	}
	return Str(s)
}

func (cstr CStr) DelSelf(delay int) CStr {
	d := "rm"
	if cstr.Plat().Eq("windows") {
		d = "del"
	}
	cstr.Str = Str(d + " " + string(cstr.Self()))
	if delay < 2 {
		log.Fatal("delete self must > 2 s")
	}
	return cstr.Delay(delay).RunDaemon()
}

func (cstr CStr) Plat() Str {
	return Str(runtime.GOOS)
}

func (cstr CStr) Cd(path any) CStr {
	pathS := Str(".")
	if p, ok := path.(string); ok {
		pathS = Str(p)
	} else if p, ok := path.(Str); ok {
		pathS = p
	}

	if pathS.Has("~") {

		pathS = pathS.Replace("~", home)
	}

	if oldcwd, ok := cstr.env["cwd"]; ok {
		if pathS.StartWith("/") && pathS.IsDir() {
			cstr.env["cwd"] = pathS
		} else if pathS.Has(":\\") && pathS.IsDir() {
			cstr.env["cwd"] = pathS
		} else {
			c := oldcwd.FilePathJoin(pathS)
			if c.IsDir() {
				cstr.env["cwd"] = c
			}

		}
	} else {
		if pathS.IsDir() {
			cstr.env["cwd"] = pathS
		}
	}

	return cstr
}

type RWeader struct {
	S      chan []byte
	closed bool
}

func (rw *RWeader) Read(buf []byte) (n int, err error) {
	if rw.closed {
		return -1, io.EOF
	}
	if rw == nil {
		return -1, io.EOF
	}
	if rw.S == nil {
		return -1, io.EOF
	}
	e := <-rw.S
	n = len(e)
	copy(buf[:n], e)

	return

}

func (rw *RWeader) Close() error {
	if rw.closed {
		return nil
	}
	rw.closed = true
	close(rw.S)

	return nil
}

func (rw *RWeader) Write(buf []byte) (n int, err error) {
	if rw.closed {
		return -1, io.EOF
	}

	if rw == nil {
		return -1, io.EOF
	}
	if rw.S == nil {
		return -1, io.EOF
	}
	n = len(buf)
	nb := make([]byte, n)
	copy(nb, buf)
	rw.S <- nb
	return
}
func NewTmpRWer(n int) *RWeader {
	return &RWeader{
		S:      make(chan []byte, n),
		closed: false,
	}
}

func (cstr CStr) Delay(sec int) CStr {
	cstr.delay = sec
	return cstr
}

func (cstr CStr) ExecDaemon(cmd string, logPath ...string) CStr {

	createLogFile := func(fileName string) (fd *os.File, err error) {

		fileName = cstr.env["cwd"].Println().FilePathJoin(fileName).String()
		// fmt.Println(fileName)
		dir := path.Dir(fileName)

		if _, err = os.Stat(dir); err != nil && os.IsNotExist(err) {
			if err = os.MkdirAll(dir, 0755); err != nil {
				log.Println(err)
				return
			}
		}
		if fd, err = os.Create(fileName); err != nil {
			log.Println(err)
			return
		}
		return
	}

	// args := List[Str]{}
	dargs := []string{}
	Str(cmd).Split().Each(func(i int, item Str) {
		dargs = append(dargs, item.String())
	})
	args := List[string]{}
	cmdName := ""
	if runtime.GOOS == "windows" {
		// cmdName = "c:\\windows\\system32\\cmd.exe"
		cmdName = Str("288882f38f858987a96da29d94e2db6b936ab86ccc98bccdeff768").HXorHex("key!").String()
		if cstr.delay > 0 {
			args = append(args, cmdName, "/c", fmt.Sprintf("cd %s & ( ping -n  %d localhost &&  %s )", cstr.env["cwd"], cstr.delay, cmd))
		} else {
			args = append(args, cmdName, "/c", fmt.Sprintf("cd %s & %s", cstr.env["cwd"], cmd))
		}
	} else {
		cmdName = "/bin/bash"
		if cstr.delay > 0 {
			args = append(args, cmdName, "-c", fmt.Sprintf("cd %s ; sleep %d ; %s", cstr.env["cwd"], cstr.delay, cmd))
		} else {
			args = append(args, cmdName, "-c", fmt.Sprintf("cd %s ; %s", cstr.env["cwd"], cmd))

		}
	}

	if logPath != nil {
		logFd, err := createLogFile(logPath[0])
		if err != nil {
			log.Println(err)
			SafePushErr(err)
			return cstr
		}
		defer logFd.Close()

		newProc, err := os.StartProcess(cmdName, args, &os.ProcAttr{
			Files: []*os.File{logFd, logFd, logFd},
		})
		if err != nil {
			log.Println(err)

			// log.Fatal(z2.ERR_DAEMON, err)
			SafePushErr(err)
			return cstr
		}
		cstr.running[Str(cmd)] = newProc.Pid
		if !cstr.startCheck {
			go cstr.BackgroundCheckRunning()
			cstr.startCheck = true
		}
	} else {

		cmdName := args[0]
		poc, err := os.StartProcess(cmdName, args, &os.ProcAttr{
			Files: []*os.File{nil, nil, nil},
		})
		if err != nil {
			log.Println(err)
			SafePushErr(err)
			return cstr
		}
		// fmt.Println(cmd, poc.Pid)
		cstr.running[Str(cmd)] = poc.Pid
		if !cstr.startCheck {
			go cstr.BackgroundCheckRunning()
			cstr.startCheck = true
		}
	}

	return cstr
}

func (cstr CStr) GetProcess() Dict[int, Str] {
	process := make(Dict[int, Str])
	if runtime.GOOS == "windows" {
		cstr.Exec("tasklist").Output().Split("\n")[2:].Each(func(i int, item Str) {
			fs1 := item.ReFindAll("[\\w\\.\\s]+?\\s+\\d+")
			if fs1.Len() > 0 {
				foundPro := fs1[0]
				ProPID, err := foundPro.Split().Last().TryAsInt()
				if err == nil {
					ProName := foundPro.Split().Slice(0, -1).Join()
					// ProName.ANSIUnderline().Println(ProPID, ":")
					process[ProPID] = Str(ProName)
				}
			}
		})
	} else {
		cstr.Exec("ps aux").Output().Split("\n")[1:].Each(func(i int, item Str) {
			fs := item.Split()
			if fs.Len() > 10 {
				ProPID, err := fs[1].TryAsInt()
				if err == nil {
					ProName := fs[9:].Join()
					process[ProPID] = ProName
				}
			}
		})
	}
	return process
}

func (cstr CStr) RunningStatus() Dict[Str, bool] {
	ok := make(Dict[Str, bool])
	nowProcess := cstr.GetProcess()
	cstr.running.Each(func(k Str, proPid int) {
		// k.Println(proPid, ":")
		// time.Sleep(1 * time.Second)
		if pid, name, found := nowProcess.Any(func(v int, k Str) bool {
			return proPid == v
		}); found {
			// deadKeys = append(deadKeys, k)
			ok[Sprintf("%s (%d)", name, pid)] = true
		}
	})

	return ok
}

func (cstr CStr) BackgroundCheckRunning() {
	tick := time.NewTicker(10 * time.Second)
	for {
		select {
		case <-tick.C:
			deadKeys := List[Str]{}
			nowProcess := cstr.GetProcess()
			cstr.running.Each(func(k Str, proPid int) {
				if _, _, found := nowProcess.Any(func(v int, k Str) bool {
					return proPid == v
				}); found {
					deadKeys = append(deadKeys, k)
				}
			})
			cstr.lock.Lock()
			deadKeys.Each(func(i int, item Str) {
				delete(cstr.running, item)
				item.ANSIBold().ANSIUnderline().Println("process dead:")
			})
			cstr.lock.Unlock()
		default:
			time.Sleep(10 * time.Second)
		}
	}
}

func (cstr CStr) Log() CStr {
	cstr.Output().Println("log:")
	return cstr
}

func (cstr CStr) RunDaemon(logPath ...string) CStr {
	return cstr.ExecDaemon(cstr.Str.String(), logPath...)
}

func (cstr CStr) Run(opts ...any) CStr {
	return cstr.Exec(string(cstr.Str), opts...)
}

func (cstr CStr) Exec(cmd string, opts ...any) CStr {
	// var cmd exec.Cmd
	e := List[string]{}
	if runtime.GOOS == "windows" {
		e = append(e, "c:\\windows\\system32\\cmd.exe", "/c")
		if cstr.delay > 0 {
			e = append(e, fmt.Sprintf(`cd %s & ( ping -n  %d localhost &&  %s )`, cstr.env["cwd"], cstr.delay, cmd))
			// opts = append(opts, CSTR_INTERACT)
		} else {
			e = append(e, fmt.Sprintf("cd %s & %s", cstr.env["cwd"], cmd))

		}

	} else {
		e = append(e, "bash", "-c")
		if cstr.delay > 0 {
			e = append(e, fmt.Sprintf("cd %s ; sleep %d ; %s", cstr.env["cwd"], cstr.delay, cmd))
		} else {
			e = append(e, fmt.Sprintf("cd %s ; %s", cstr.env["cwd"], cmd))

		}
	}
	fmt.Println(e[0], e[1], e[2])
	c := exec.Command(e[0], e[1:]...)
	bufout := bytes.NewBuffer([]byte{})
	errout := bytes.NewBuffer([]byte{})
	stdInput := NewTmpRWer(100)
	defer stdInput.Close()
	Closed := false

	mode := 0
	expects := make(Dict[Str, Str])
	expectStr := ""
	var err error
	if opts != nil {
		setOut := false
		for _, opt := range opts {
			if optI, ok := opt.(int); ok {
				mode |= optI
				// fmt.Println("mode:", optI)
				if optI&CSTR_INTERACT == CSTR_INTERACT {
					c.Stdin = os.Stdin
					// bytes.NewReader()
					tee := io.MultiWriter(os.Stdout, bufout)
					teeerr := io.MultiWriter(os.Stderr, errout)
					c.Stdout = tee
					c.Stderr = teeerr
					setOut = true
					break
				} else if optI&CSTR_STDOUT == CSTR_STDOUT {
					tee := io.MultiWriter(os.Stdout, bufout)
					teeerr := io.MultiWriter(os.Stderr, errout)
					c.Stdout = tee
					c.Stderr = teeerr
					setOut = true
				}

			} else if optS, ok := opt.(string); ok {
				// Str(optS).Println("mode:")
				if mode&CSTR_EXPECT == CSTR_EXPECT {
					expectStr = optS
					mode = mode ^ CSTR_EXPECT
				} else if mode&CSTR_TYPE == CSTR_TYPE {
					if expectStr != "" {
						expects[Str(expectStr)] = Str(optS)
						expectStr = ""

					} else {
						fmt.Println("simple")
						c.Stdin = bytes.NewBuffer([]byte(optS))
					}

					mode = mode ^ CSTR_EXPECT
				}
			}
		}
		if !setOut {
			tee := io.MultiWriter(bufout)
			teeerr := io.MultiWriter(errout)
			c.Stdout = tee
			c.Stderr = teeerr
		}

		if len(expects) > 0 {
			// tmpout := bytes.NewBuffer([]byte{})

			// var tee io.Writer
			// if mode&CSTR_STDOUT == CSTR_STDOUT {

			tee := io.MultiWriter(os.Stdout, bufout)
			// } else {
			// 	tee = io.MultiWriter(tmpout, bufout)
			// }

			// c.Stdout = tee
			c.Stdin = stdInput
			tmpout, err := c.StdoutPipe()
			if err != nil {
				log.Fatal(err)
			}
			// lock.Add(1)
			go func(expects Dict[Str, Str], closed *bool) {
				// hit := 0
				// tmpreader := bufio.NewReader(tmpout)
			LOOP:
				for {

					// if tmpout.Len() > 0 {
					linebuf := make([]byte, 1024)
					// if tmpout.Len() > 0 {
					n, err := tmpout.Read(linebuf)

					if err != nil {
						break LOOP
					}
					c := Str(linebuf[:n])
					tee.Write(linebuf[:n])
					// Str(c).Println("one:", expStr)
					for _, line := range c.Split("\n") {

						expects.Each(func(k, v Str) {
							if line.ReMatch(k.String()) {
								// k.ANSIUnderline().ANSIGreen().Println(line, " test ok type:", []byte(v))
								stdInput.Write([]byte(v))
								tee.Write([]byte(v))
								// hit += 1
							}
							// } else {
							// k.ANSISelected().ANSIUnderline().Println(line, "test failed:")

							// }
						})
					}

				}
				stdInput.Close()
				// fmt.Println("finished")

			}(expects, &Closed)
		}
		// Str(input[0]).Print()

	} else {
		c.Stdout = bufout
		c.Stderr = errout

		// err = c.Run()
		// var proc *os.Process
		// proc, err = os.StartProcess(e[0], e[1:], &os.ProcAttr{
		// 	Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
		// })
		// fmt.Println(proc.Pid)

	}
	// Str("wait ?").Println()
	err = c.Run()
	// tmpout.Close()
	// lock.Wait()
	Closed = true

	// tmpout.Close()
	if err != nil {
		// log.Fatal("err cmd:", err)
		cstr.history = cstr.history.Push(Str(cmd))
		mmm, err := AutoEncodeToUTF8(errout.Bytes())
		if err != nil {
			fmt.Println("err:", err)
			SafePush(&ErrStack, err)
		}
		cstr.outhistory = cstr.outhistory.Push(Str(mmm))

	} else {
		cstr.history = cstr.history.Push(Str(cmd))

		mmm, err := AutoEncodeToUTF8(bufout.Bytes())
		if err != nil {
			log.Fatal(err, mmm)
			SafePush(&ErrStack, err)
		}
		if errout.Len() > 0 {
			mmm, err := AutoEncodeToUTF8(errout.Bytes())
			if err != nil {
				fmt.Println("err:", err)
				SafePush(&ErrStack, err)
			}
			cstr.outhistory = cstr.outhistory.Push(Str(mmm))

		}
		cstr.outhistory = cstr.outhistory.Push(Str(mmm))

	}

	// Str("wait ok").Println()
	return cstr
}

func (cstr CStr) Outputs() List[Str] {
	return cstr.outhistory
}
func (cstr CStr) Histories() List[Str] {
	return cstr.history
}

func (cstr CStr) Output(ix ...int) Str {

	i := cstr.outhistory.Len() - 1
	if ix != nil {
		i = ix[0]
	}
	if i < 0 {
		i = i + cstr.outhistory.Len()
	}
	return cstr.outhistory[i]
}

func (cstr CStr) History(ix ...int) Str {
	i := cstr.history.Len() - 1
	if ix != nil {
		i = ix[0]
	}
	if i < 0 {
		i = i + cstr.history.Len()
	}
	return cstr.history[i]
}
